/// @file ObsID.cpp
/// gpstk::ObsID - Identifies types of observations

//============================================================================
//
//  This file is part of GPSTk, the GPS Toolkit.
//
//  The GPSTk is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published
//  by the Free Software Foundation; either version 2.1 of the License, or
//  any later version.
//
//  The GPSTk is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with GPSTk; if not, write to the Free Software Foundation,
//  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
//
//  Copyright 2004, The University of Texas at Austin
//
//============================================================================


#include "ObsID.hpp"

namespace gpstk
{
    // descriptions (strings) of each code, carrier and type
    std::map< ObsID::TrackingCode,    std::string > ObsID::tcDesc;
    std::map< ObsID::CarrierBand,     std::string > ObsID::cbDesc;
    std::map< ObsID::ObservationType, std::string > ObsID::otDesc;
    // mappings between code, carrier, type and characters
    std::map< char, ObsID::ObservationType> ObsID::char2ot;
    std::map< char, ObsID::CarrierBand> ObsID::char2cb;
    std::map< char, ObsID::TrackingCode> ObsID::char2tc;
    std::map< ObsID::ObservationType, char > ObsID::ot2char;
    std::map< ObsID::CarrierBand, char > ObsID::cb2char;
    std::map< ObsID::TrackingCode, char> ObsID::tc2char;

    // map of valid RINEX tracking codes, systems and frequency
    std::map<char, std::map<char, std::string> > ObsID::validRinexTrackingCodes;

    // string containing the system characters for all valid RINEX systems.
    std::string ObsID::validRinexSystems;

    // maps between 1-char and 3-char system id
    std::map<std::string, std::string> ObsID::map1to3sys;
    std::map<std::string, std::string> ObsID::map3to1sys;

    // string containing the frequency digits for all valid RINEX systems.
    std::string ObsID::validRinexFrequencies;

    // object that forces initialization of the maps
    ObsIDInitializer singleton;

    // Construct this object from the string specifier
    ObsID::ObsID(const std::string& strID) noexcept(false)
    {
        int i = strID.length() - 3;
        if ( i < 0 || i > 1)
        {
            InvalidParameter e("identifier must be 3 or 4 characters long");
            GPSTK_THROW(e);
        }

        char sys = i ? strID[0] : 'G';
        char ot = strID[i];
        char cb = strID[i+1];
        char tc = strID[i+2];

        if (!char2ot.count(ot) || !char2cb.count(cb) || !char2tc.count(tc))
            idCreator(strID.substr(i,3));

        type = char2ot[ ot ];
        band = char2cb[ cb ];
        code = char2tc[ tc ];
    }


    // Convenience output method
    std::ostream& ObsID::dump(std::ostream& s) const
    {
        s << ObsID::cbDesc[band] << " "
          << ObsID::tcDesc[code] << " "
          << ObsID::otDesc[type];
        return s;
    } // ObsID::dump()




    // This is used to register a new ObsID & Rinex 3 identifier.  The syntax for the
    // Rinex 3 identifier is the same as for the ObsID constructor.
    // If there are spaces in the provided identifier, they are ignored
    ObsID ObsID::newID(const std::string& strID, const std::string& desc)
        noexcept(false)
    {
        if (char2ot.count(strID[0]) &&
            char2cb.count(strID[1]) &&
            char2tc.count(strID[2]))
            GPSTK_THROW(InvalidParameter("Identifier " + strID + " already defined."));

        return idCreator(strID, desc);
    }


    ObsID ObsID::idCreator(const std::string& strID, const std::string& desc)
    {
        char ot = strID[0];
        ObservationType type;
        if (!char2ot.count(ot))
        {
            type = (ObservationType)otDesc.size();
            otDesc[type] = desc;
            char2ot[ot] = type;
            ot2char[type] = ot;
        }
        else
            type = char2ot[ot];

        char cb = strID[1];
        CarrierBand band;
        if (!char2cb.count(cb))
        {
            band = (CarrierBand)cbDesc.size();
            cbDesc[band] = desc;
            char2cb[cb] = band;
            cb2char[band] = cb;
        }
        else
            band = char2cb[cb];

        char tc = strID[2];
        TrackingCode code;
        if (!char2tc.count(tc))
        {
            code = (TrackingCode) tcDesc.size();
            tcDesc[code] = desc;
            char2tc[tc] = code;
            tc2char[code] = tc;
        }
        else
            code = char2tc[tc];

        return ObsID(type, band, code);
    }


    // Equality requires all fields to be the same unless the field is unknown
    bool ObsID::operator==(const ObsID& right) const
    {
        bool ot = type == otAny || right.type == otAny || type == right.type;
        bool cb = band == cbAny || right.band == cbAny || band == right.band;
        bool tc = code == tcAny || right.code == tcAny || code == right.code;
        return ot && cb && tc;
    }


    // This ordering is somewhat arbitrary but is required to be able
    // to use an ObsID as an index to a std::map. If an application needs
    // some other ordering, inherit and override this function.
    bool ObsID::operator<(const ObsID& right) const
    {
        if (band == right.band)
            if (code == right.code)
                return type < right.type;
            else
                return code < right.code;
        else
            return band < right.band;

        // This should never be reached...
        return false;
    }


    namespace StringUtils
    {
        // convert this object to a string representation
        std::string asString(const ObsID& p)
        {
            std::ostringstream oss;
            p.dump(oss);
            return oss.str();
        }
    }


    // stream output for ObsID
    std::ostream& operator<<(std::ostream& s, const ObsID& p)
    {
        p.dump(s);
        return s;
    }

}
